use super::*;

/// Represents a reference to a JavaScript value
pub struct Reference(sys::napi_env, sys::napi_ref, u32);
impl Reference {
	/// Creates a new reference to a JavaScript value with an initial reference count of 1
	pub fn new<'val>(v: Value<'val>) -> Result<'static, Self> {
		Self::with_initial_refcount(v, 1)
	}
	/// Creates a new reference to a JavaScript value with a specified initial reference count
	pub fn with_initial_refcount<'val>(v: Value<'val>, initial_refcount: u32) -> Result<'static, Reference> {
		let mut ret = core::ptr::null_mut();
		unsafe { sys::napi_create_reference(v.env.raw(), v.inner, initial_refcount, &mut ret) }.result("napi_create_reference")?;
		Ok(Reference(v.env.raw(), ret, initial_refcount))
	}
}

impl Drop for Reference {
	/// Decrements the reference count and deletes the reference if necessary
	fn drop(&mut self) {
		let mut ret = 0;
		let env = Env::from(self.0);
		unsafe { sys::napi_reference_unref(self.0, self.1, &mut ret) }
			.result("napi_reference_unref")
			.throw(env);
		if ret < self.2 || ret == 0 {
			unsafe { sys::napi_delete_reference(self.0, self.1) }
				.result("napi_delete_reference")
				.throw(env);
		}
	}
}

impl Clone for Reference {
	/// Increments the reference count
	fn clone(&self) -> Self {
		let mut ret = 0;
		let env = Env::from(self.0);
		unsafe { sys::napi_reference_ref(self.0, self.1, &mut ret) }
			.result("napi_reference_ref")
			.throw(env);
		Reference(self.0, self.1, self.2)
	}
}

impl<'val> FromNapi<'val> for Reference {
	/// Creates a new reference to a JavaScript value with an initial reference count of 1
	fn from_napi(v: Value<'val>) -> Result<'static, Self> {
		Self::with_initial_refcount(v, 1)
	}
}

impl IntoNapi for Reference {
	/// Converts a Reference to a Value
	fn into_napi<'env>(self, env: &'env Env) -> Result<'static, Value<'env>> {
		if self.0 != env.raw() {
			return Err(Status::invalid_arg("Ref env not eq to arg env"));
		}
		let mut ret = core::ptr::null_mut();
		unsafe { sys::napi_get_reference_value(env.raw(), self.1, &mut ret) }.result("napi_get_reference_value")?;
		Ok(Value::new(env, ret))
	}
}
